﻿using Sidvall.IO.Data;
using Sidvall.Web.IO.Data;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;

namespace Sidvall.Web.IO.Controllers
{
    public class FileController : ApiController
    {
        #region Public Members

        #region Copy

        [HttpPost]
        [Route("api/V1/NetFileSystem/{StoreId}/File/Copy")]
        public async Task<HttpResponseMessage> Copy(string storeId, [FromBody]CopyItemContext criteria)
        {
            HttpResponseMessage message;

            if (criteria == null)
                throw new System.ArgumentNullException("criteria");
            try
            {
                var result = await SystemContext.Current.FileManager.CopyFileAsync(storeId, criteria.SourcePath, criteria.DestinationPath, criteria.DeleteExisting, CancellationToken.None);
                message = this.Request.CreateResponse(HttpStatusCode.OK, result);
            }
            catch (System.Exception ex)
            {
                await SystemContext.Current.ControllerManager.LogAsync(ex);
                message = SystemContext.Current.ControllerManager.GetResponseMessage(this, ex);
            }
            return message;
        }

        #endregion
        #region Delete

        [HttpPost]
        [Route("api/V1/NetFileSystem/{StoreId}/File/Delete")]
        public async Task<HttpResponseMessage> Delete(string storeId, [FromBody]ItemContext criteria)
        {
            HttpResponseMessage message;

            if (criteria == null)
                throw new System.ArgumentNullException("criteria");
            try
            {
                var result = await SystemContext.Current.FileManager.DeleteFileAsync(storeId, criteria.Path, CancellationToken.None);
                message = this.Request.CreateResponse(HttpStatusCode.OK, result);
            }
            catch (System.Exception ex)
            {
                await SystemContext.Current.ControllerManager.LogAsync(ex);
                message = SystemContext.Current.ControllerManager.GetResponseMessage(this, ex);
            }
            return message;
        }

        #endregion
        #region Exists

        [HttpPost]
        [Route("api/V1/NetFileSystem/{StoreId}/File/Exists")]
        public async Task<HttpResponseMessage> Exists(string storeId, [FromBody]ItemContext criteria)
        {
            HttpResponseMessage message;

            if (criteria == null)
                throw new System.ArgumentNullException("criteria");
            try
            {
                var result = await SystemContext.Current.FileManager.FileExistsAsync(storeId, criteria.Path, CancellationToken.None);
                message = this.Request.CreateResponse(HttpStatusCode.OK, result);
            }
            catch (System.Exception ex)
            {
                await SystemContext.Current.ControllerManager.LogAsync(ex);
                message = SystemContext.Current.ControllerManager.GetResponseMessage(this, ex);
            }
            return message;
        }

        #endregion
        #region GetItem

        [HttpPost]
        [Route("api/V1/NetFileSystem/{StoreId}/File/GetItem")]
        public async Task<HttpResponseMessage> GetItem(string storeId, [FromBody]GetItemContext criteria)
        {
            HttpResponseMessage message;

            if (criteria == null)
                throw new System.ArgumentNullException("criteria");
            try
            {
                var result = await SystemContext.Current.FileManager.GetFileAsync(storeId, criteria.Path, criteria.LoadDetails, CancellationToken.None);
                if (result != null)
                    message = this.Request.CreateResponse(HttpStatusCode.OK, result);
                else
                    message = this.Request.CreateResponse(HttpStatusCode.NoContent);
            }
            catch (System.Exception ex)
            {
                await SystemContext.Current.ControllerManager.LogAsync(ex);
                message = SystemContext.Current.ControllerManager.GetResponseMessage(this, ex);
            }
            return message;
        }

        #endregion
        #region GetItems

        [HttpPost]
        [Route("api/V1/NetFileSystem/{StoreId}/File/GetItems")]
        public async Task<HttpResponseMessage> GetItems(string storeId, [FromBody]GetItemsContext criteria)
        {
            HttpResponseMessage message;

            if (criteria == null)
                throw new System.ArgumentNullException("criteria");
            try
            {
                var result = await SystemContext.Current.FileManager.GetFilesAsync(storeId, criteria.Path, criteria.SearchPattern, criteria.SearchOption, criteria.LoadDetails, CancellationToken.None);
                if ((result != null) && (result.Any()))
                    message = this.Request.CreateResponse(HttpStatusCode.OK, result.ToArray());
                else
                    message = this.Request.CreateResponse(HttpStatusCode.NoContent);
            }
            catch (System.Exception ex)
            {
                await SystemContext.Current.ControllerManager.LogAsync(ex);
                message = SystemContext.Current.ControllerManager.GetResponseMessage(this, ex);
            }
            return message;
        }

        #endregion
        #region GetNames

        [HttpPost]
        [Route("api/V1/NetFileSystem/{StoreId}/File/GetNames")]
        public async Task<HttpResponseMessage> GetNames(string storeId, [FromBody]GetNamesContext criteria)
        {
            HttpResponseMessage message;

            if (criteria == null)
                throw new System.ArgumentNullException("criteria");
            try
            {
                var result = await SystemContext.Current.FileManager.GetFileNamesAsync(storeId, criteria.Path, criteria.SearchPattern, criteria.SearchOption, CancellationToken.None);
                if ((result != null) && (result.Any()))
                    message = this.Request.CreateResponse(HttpStatusCode.OK, result.ToArray());
                else
                    message = this.Request.CreateResponse(HttpStatusCode.NoContent);
            }
            catch (System.Exception ex)
            {
                await SystemContext.Current.ControllerManager.LogAsync(ex);
                message = SystemContext.Current.ControllerManager.GetResponseMessage(this, ex);
            }
            return message;
        }

        #endregion
        #region GetSize

        [HttpPost]
        [Route("api/V1/NetFileSystem/{StoreId}/File/GetSize")]
        public async Task<HttpResponseMessage> GetSize(string storeId, [FromBody]ItemContext criteria)
        {
            HttpResponseMessage message;
            long fileLength;

            if (criteria == null)
                throw new System.ArgumentNullException(nameof(criteria));
            try
            {
                using (var fileStream = await SystemContext.Current.FileManager.OpenFileAsync(storeId, OpenFileContext.Create(criteria), Sidvall.IO.FileMode.Open,
                    Sidvall.IO.FileAccess.Read, CancellationToken.None))
                {
                    fileLength = fileStream.Length;
                }
                message = this.Request.CreateResponse(HttpStatusCode.OK, fileLength);
            }
            catch (System.Exception ex)
            {
                await SystemContext.Current.ControllerManager.LogAsync(ex);
                message = SystemContext.Current.ControllerManager.GetResponseMessage(this, ex);
            }
            return message;
        }

        #endregion
        #region Move

        [HttpPost]
        [Route("api/V1/NetFileSystem/{StoreId}/File/Move")]
        public async Task<HttpResponseMessage> Move(string storeId, [FromBody]CopyItemContext criteria)
        {
            HttpResponseMessage message;

            if (criteria == null)
                throw new System.ArgumentNullException("criteria");
            try
            {
                var result = await SystemContext.Current.FileManager.MoveFileAsync(storeId, criteria.SourcePath, criteria.DestinationPath, criteria.DeleteExisting, CancellationToken.None);
                message = this.Request.CreateResponse(HttpStatusCode.OK, result);
            }
            catch (System.Exception ex)
            {
                await SystemContext.Current.ControllerManager.LogAsync(ex);
                message = SystemContext.Current.ControllerManager.GetResponseMessage(this, ex);
            }
            return message;
        }

        #endregion

        #region Flush

        [HttpPost]
        [Route("api/V1/NetFileSystem/{StoreId}/File/Flush")]
        public async Task<HttpResponseMessage> Flush(string storeId, [FromBody]ItemContext criteria)
        {
            HttpResponseMessage message;

            if (criteria == null)
                throw new System.ArgumentNullException(nameof(criteria));
            try
            {
                await SystemContext.Current.FileManager.FlushAsync(storeId, OpenFileContext.Create(criteria), CancellationToken.None);
                message = this.Request.CreateResponse(HttpStatusCode.OK);
            }
            catch (System.Exception ex)
            {
                await SystemContext.Current.ControllerManager.LogAsync(ex);
                message = SystemContext.Current.ControllerManager.GetResponseMessage(this, ex);
            }
            return message;
        }

        #endregion
        #region Read

        [HttpPost]
        [Route("api/V1/NetFileSystem/{StoreId}/File/Read")]
        public async Task<HttpResponseMessage> Read(string storeId, [FromBody]ReadFileContext criteria)
        {
            return await Sidvall.Web.IO.SystemContext.Current.ControllerManager.GetStreamContentAsync(this, criteria.Path, null, storeId, criteria.Length, criteria.Position);
        }

        [HttpGet]
        [Route("api/V1/NetFileSystem/{StoreId}/File/Read/{*path}")]
        public async Task<HttpResponseMessage> Read(string storeId, string path)
        {
            return await Sidvall.Web.IO.SystemContext.Current.ControllerManager.GetStreamContentAsync(this, path, null, storeId);
        }

        #endregion
        #region ReadHead

        [HttpHead]
        [Route("api/V1/NetFileSystem/{StoreId}/File/Read/{*path}")]
        public async Task<HttpResponseMessage> ReadHead(string storeId, string path)
        {
            HttpResponseMessage message;
            long fileLength;

            if (string.IsNullOrWhiteSpace(path))
                throw new System.ArgumentNullException(nameof(path));
            try
            {
                using (var fileStream = await SystemContext.Current.FileManager.OpenFileAsync(storeId, OpenFileContext.Create(path), Sidvall.IO.FileMode.Open,
                    Sidvall.IO.FileAccess.Read, CancellationToken.None))
                {
                    fileLength = fileStream.Length;
                }
                message = new HttpResponseMessage();
                Sidvall.Web.IO.SystemContext.Current.ControllerManager.SetResponseHeaders(message, System.IO.Path.GetFileName(path), null, fileLength, fileLength,
                    this.Request.Headers.Range);
            }
            catch (System.Exception ex)
            {
                await SystemContext.Current.ControllerManager.LogAsync(ex);
                message = SystemContext.Current.ControllerManager.GetResponseMessage(this, ex);
            }
            return message;
        }

        #endregion
        #region WriteBuffer

        [HttpPost]
        [Route("api/V1/NetFileSystem/{StoreId}/File/WriteBuffer")]
        public async Task<HttpResponseMessage> WriteBuffer(string storeId, [FromBody]WriteFileContext criteria)
        {
            HttpResponseMessage message;
            Sidvall.IO.FileMode fileMode;

            if (criteria == null)
                throw new System.ArgumentNullException("criteria");
            try
            {
                if (criteria.Position == 0)
                    fileMode = Sidvall.IO.FileMode.Create;
                else
                    fileMode = Sidvall.IO.FileMode.Open;
                using (var fileStream = await SystemContext.Current.FileManager.OpenFileAsync(storeId, OpenFileContext.Create(criteria), fileMode,
                    Sidvall.IO.FileAccess.Write, CancellationToken.None))
                {
                    fileStream.Position = criteria.Position;
                    await fileStream.WriteAsync(criteria.Buffer, 0, criteria.Buffer.Length);
                }
                message = this.Request.CreateResponse(HttpStatusCode.Created);
            }
            catch (System.Exception ex)
            {
                await SystemContext.Current.ControllerManager.LogAsync(ex);
                message = SystemContext.Current.ControllerManager.GetResponseMessage(this, ex);
            }
            return message;
        }

        #endregion
        #region WriteStream

        [HttpPost]
        [Route("api/V1/NetFileSystem/{StoreId}/File/WriteStream/{*path}")]
        public async Task<HttpResponseMessage> WriteStream(string storeId, string path)
        {
            HttpResponseMessage message;
            HttpStatusCode statusCode;

            ThrowIfNotValidPath(path);
            try
            {
                statusCode = HttpStatusCode.OK;
                var parts = await this.Request.Content.ReadAsMultipartAsync();
                foreach (var content in parts.Contents)
                {
                    var fileName = GetFileName(content.Headers);
                    if (string.IsNullOrWhiteSpace(fileName))
                        continue;
                    var context = OpenFileContext.Create(await GetPathAsync(storeId, path, fileName));
                    using (var stream = await content.ReadAsStreamAsync())
                    {
                        using (var fileStream = await SystemContext.Current.FileManager.OpenFileAsync(storeId, context, Sidvall.IO.FileMode.Create,
                            Sidvall.IO.FileAccess.Write, CancellationToken.None))
                        {
                            await stream.CopyToAsync(fileStream);
                        }
                    }
                    statusCode = HttpStatusCode.Created;
                }
                message = this.Request.CreateResponse(statusCode);
            }
            catch (System.Exception ex)
            {
                await SystemContext.Current.ControllerManager.LogAsync(ex);
                message = SystemContext.Current.ControllerManager.GetResponseMessage(this, ex);
            }
            return message;
        }

        #endregion

        #endregion
        #region Private Members

        #region GetFileName

        private static string GetFileName(HttpContentHeaders headers)
        {
            if ((headers == null) || (headers.ContentDisposition == null))
                return null;
            var fileName = headers.ContentDisposition.FileName;
            if (string.IsNullOrWhiteSpace(fileName))
                return null;
            fileName = fileName.Trim(new char[] { ' ', '"' });
            if (string.IsNullOrWhiteSpace(fileName))
                return null;
            return System.IO.Path.GetFileName(fileName);
        }

        #endregion
        #region GetPathAsync

        private static async Task<string> GetPathAsync(string storeId, string rootPath, string path)
        {
            if (string.IsNullOrWhiteSpace(rootPath))
                return path;
            if (string.IsNullOrWhiteSpace(path))
                return rootPath;
            if (System.IO.Path.IsPathRooted(path))
                return path;
            return await SystemContext.Current.FileManager.CombineAsync(storeId, rootPath, path);
        }

        #endregion
        #region ThrowIfNotValidPath

        public void ThrowIfNotValidPath(string path)
        {
            if (string.IsNullOrWhiteSpace(path))
                return;
            if (System.IO.Path.IsPathRooted(path))
                throw new System.ArgumentException("Invalid path");
        }

        #endregion

        #endregion
    }
}
